/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <algorithm>

#ifdef LINUX_OS
#include <sys/io.h>
#endif

#include "ioportstd.hpp"
#include "owndebug.h"


//*****************************************************************************
//*****************************************************************************

CIOPortStd::CIOPortStd()
  :  iPermissions(),
     iPermissionsArray(),
     iBoundAddress(),
     iIsOpen(false)
{
  dPrint(5,"Constructed");
}
//*****************************************************************************

CIOPortStd::~CIOPortStd()
{
  Close();
  dPrint(5,"Destructed");
}
//*****************************************************************************

bool CIOPortStd::SetRange(unsigned long from, unsigned long num, int turn_on)
{

  SPermissionEntry range(from, num, turn_on);

  if (from+num > 0x3FFF) {
    dPrint(4, "Out of range! 0x%03x-0x%03x is not inside 0x000 - 0x3FFF!",
	   from, from+num);
    return false;
  }

  // Finally, assign the local permission mask
  Lock();
  iPermissions = range;
  Unlock();

  return true;
}
//*****************************************************************************

bool CIOPortStd::BindAddr(uint16_t aAddress)
{
  Lock();
  if (aAddress < iPermissions.from ||
      aAddress > iPermissions.from+iPermissions.num) {
    dPrint(4,"Access not allowed outside own permission range! (Have you 'Open()':ed?)");
    Unlock();
    return false;
  }

  iBoundAddress = aAddress;

  Unlock();

  return true;
}
//*****************************************************************************


CASRoboBus::EBusStatus CIOPortStd::Open(void)
{
  if (iIsOpen) {
    dPrint(2,"CIOPort instance already open - will not open again.");
    return KBusStatusError;
  }

  dPrint(6,"Opening new Bus; (ASRobo_IOPort), range: 0x%03x-0x%03x",
	 iPermissions.from, iPermissions.from+iPermissions.num);


  Lock();
  ITERATOR(iPermissionsArray) i = find(iPermissionsArray.begin(),
				       iPermissionsArray.end(),
				       iPermissions);
  if (i != iPermissionsArray.end() ) {
    dPrint(1,"ERROR: Port range 0x%03x-0x%03x intersects with port range 0x%03x-0x%03x!",
	   iPermissions.from,iPermissions.from+iPermissions.num,
	   i->from, i->from+i->num);
    Unlock();
    return KBusStatusError;

  } else {

    // Was not found, so add it!
    iPermissionsArray.push_back(iPermissions);
  }
  Unlock();

  int r = Ioperm(iPermissions.from, iPermissions.num, iPermissions.turn_on);

  if (r == 0) {
    // Defaulting the 'bound address' to base address.
    iBoundAddress = iPermissions.from;
    iIsOpen = true;

    return KBusStatusOK;
  } else {
    dPrint(3,"Unable to set ioperm for port range 0x%03x-0x%03x!",
	   iPermissions.from, iPermissions.from+iPermissions.num);

    return KBusStatusError;
  }
}
//*****************************************************************************

int CIOPortStd::Read(unsigned char *buffer, int buffer_size)
{
#ifdef LINUX_OS
  Lock();

  if (iBoundAddress == 0xFFFF) {
    dPrint(3, "No address bound! Cannot Read()!");
    Unlock();
    return -1;
  }

  for (int i=0; i < buffer_size; i++) {
    buffer[i] = ::inb(iBoundAddress);
  }

  Unlock();
#endif
  return buffer_size;
}
//*****************************************************************************

int CIOPortStd::Write(const unsigned char *buffer, int buffer_size)
{
#ifdef LINUX_OS
  Lock();

  if (iBoundAddress == 0xFFFF) {
    dPrint(2, "No address bound! Cannot Write()!");
    Unlock();
    return -1;
  }

  for (int i=0; i < buffer_size; i++) {
    ::outb(buffer[i], iBoundAddress);
  }

  Unlock();
#endif

  return buffer_size;
}
//*****************************************************************************

CASRoboBus::EBusStatus CIOPortStd::WaitToRead(const int aTimeout_ms)
{
  EBusStatus status = KBusStatusUnimplemented;
  return status;
}
//*****************************************************************************

CASRoboBus::EBusStatus CIOPortStd::WaitToWrite(const int aTimeout_ms)
{
  EBusStatus status = KBusStatusUnimplemented;
  return status;
}
//*****************************************************************************

CASRoboBus::EBusStatus CIOPortStd::Close(void)
{
  if (iIsOpen) {
    dPrint(2,"CIOPort instance not open - will not close.");
    return KBusStatusError;
  }

  dPrint(4,"Closing IOPort with port 0x%03x-0x%03x.",
	 iPermissions.from, iPermissions.from+iPermissions.num);

  // Set ioperm to 'no access'
  Ioperm(iPermissions.from, iPermissions.num, 0);

  Lock();

  // Remove the entry
  ITERATOR(iPermissionsArray) i = find(iPermissionsArray.begin(),
				       iPermissionsArray.end(),
				       iPermissions);
  if (! (i == iPermissionsArray.end())) {
    iPermissionsArray.erase(i);
  } else {
    dPrint(2,"WARNING: Unable to find entry for this instance. (range 0x%03x-0x%03x) Not Open():ed?",
	   iPermissions.from, iPermissions.from+iPermissions.num);

  }

  Unlock();

  return KBusStatusOK;
}
//*****************************************************************************

int CIOPortStd::Inb(uint16_t aAddress)
{
  if (iIsOpen) {
#ifdef LINUX_OS
    if (aAddress < iPermissions.from ||
        aAddress > iPermissions.from+iPermissions.num) {
      dPrint(10,"Access not allowed outside own permission range! (Range 0x%03x-0x%03x, requested 0x%03x)",
             iPermissions.from, iPermissions.from+iPermissions.num, aAddress);
      ownSleep_ms(500);
      return -1;
    }
    int value = ::inb(aAddress);
    dPrint(20,"inb returnvalue = %d",value);
    return value;
#else
    return 0;
#endif
  } else {
    dPrint(2,"WARNING: Inb called on Closed interface!");
    return -1;
  }
}
//*****************************************************************************

int CIOPortStd::Outb(uint8_t aByte, uint16_t aAddress)
{
  if (iIsOpen) {
#ifdef LINUX_OS
    if (aAddress < iPermissions.from ||
        aAddress > iPermissions.from+iPermissions.num) {
      dPrint(10,"Access not allowed outside own permission range! (Range 0x%03x-0x%03x, requested 0x%03x)",
             iPermissions.from, iPermissions.from+iPermissions.num, aAddress);
      ownSleep_ms(500);
      return -1;
    }
    ::outb(aByte, aAddress);
#endif
  } else {
    dPrint(2,"WARNING: Outb called on Closed interface!");
    return -1;
  }
  return 0;
}
//*****************************************************************************

int CIOPortStd::Inw(uint16_t aAddress)
{
  if (iIsOpen) {
#ifdef LINUX_OS
    if (aAddress < iPermissions.from ||
        aAddress > iPermissions.from+iPermissions.num-1) {
      dPrint(10,"Access not allowed outside own permission range!");
      return -1;
    }
    int value = ::inw(aAddress);
    
    return value;
#else
    return 0;
#endif
  } else {
    dPrint(2,"WARNING: Inw called on Closed interface!");
    return -1;
    
  }
}
//*****************************************************************************

int CIOPortStd::Outw(uint16_t aWord, uint16_t aAddress)
{
  if (iIsOpen) {
#ifdef LINUX_OS
    if (aAddress < iPermissions.from ||
        aAddress > iPermissions.from+iPermissions.num-1) {
      dPrint(10,"Access not allowed outside own permission range!");
      return -1;
    }
    ::outw(aWord, aAddress);
#endif
    return 0;
    
  } else {
    dPrint(2,"WARNING: Outw called on Closed interface!");
    return -1;
    
  }
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

int CIOPortStd::Ioperm(unsigned long from, unsigned long num, int turn_on)
{
#ifdef LINUX_OS
  Lock();
  dPrint(10,"Calling ioperm(0x%03x, %d, %d)",
	 from, num, turn_on);
  int r = ::ioperm(from, num, turn_on);
  Unlock();

  if (r != 0) {
    dPrint(3,"Warning: 'ioperm' call failed for range 0x%03x - 0x%03x",
	   from, from+num);
  }

  return r;
#else
  return 0;
#endif
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
